package mastermind;

import java.io.IOException;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.LinkedHashMap;
import java.util.Map;

import mastermind.game.GameException;
import mastermind.game.Pieces;
import mastermind.game.Player;
import mastermind.net.GameStream;
import mastermind.net.Protocol;

public class Client implements Protocol {
	private Socket socket;
	private GameStream conn;
	private String sessionKey;
	
	/**
	 * Constructor for Client
	 * @throws UnknownHostException
	 * @throws IOException
	 */
	public Client() throws UnknownHostException, IOException {
		socket = new Socket("localhost", SERVER_DEFAULT_PORT);
		conn = new GameStream(socket);
		sessionKey = "";
	}
	
	/**
	 * Log's out a player from the server
	 * @param username
	 * @return Boolean
	 */
	public boolean logout(String username) {
		try {
			System.out.println("sending " + OpCode.LOGOUT);
			conn.sendOpCode(OpCode.LOGOUT);
			conn.out.writeUTF(username);
			conn.out.writeUTF(sessionKey);

			OpCode op = conn.readOpCode();
			System.out.println("LOGOUT: response: " + op);

			if (op.equals(OpCode.SUCCESS)) {
				System.out.println("LOGOUT: closing connection");
				conn.closeAll();
				return true;
			}
		} catch (Exception e) {
			System.err.println("logout: " + e.getMessage());
		}
		return false;
	}
	
	/**
	 * Attempt to guess the value of the secret with given pieces. Returns a
	 * feedback string of b & w.
	 * 
	 * @param username
	 *            Username of guessing player
	 * @param p
	 *            Pieces for guess
	 * @return String, the feedback of the guess, b & w.
	 * @throws GameException
	 */
	public String guess(String username, Pieces p) throws GameException {
		try {
			System.out.println("sending " + OpCode.GUESS);
			conn.sendOpCode(OpCode.GUESS);
			conn.out.writeUTF(username);
			conn.out.writeUTF(sessionKey);
			conn.writeObject(p);
			
			OpCode op = conn.readOpCode();
			System.out.println("GUESS response: " + op);
			if (op.equals(OpCode.SUCCESS)) {
				String feedback = conn.in.readUTF();
				return feedback;
			} else {
				String error = conn.in.readUTF();
				throw new GameException(error);
			}
		} catch (IOException e) {
			System.err.println("guess: " + e.getMessage());
		}
		return null;
	}
	
	/**
	 * Starts a game, specified by username
	 * @param username
	 * @return
	 */
	public boolean startGame(String username) {
		try {
			System.out.println("sending " + OpCode.START_GAME);
			conn.sendOpCode(OpCode.START_GAME);

			conn.out.writeUTF(username);
			conn.out.writeUTF(sessionKey);

			OpCode op = conn.readOpCode();
			System.out.println("START_GAME response: " + op);
			return op.equals(OpCode.SUCCESS) ? true : false;
		} catch (Exception e) {
			System.err.println("startGame: " + e.getMessage());
		}
		return false;
	}
	
	/**
	 * Attempts to login a user
	 * 
	 * @param user
	 * @param password
	 * @return Map<Pieces, String> The saved game state, may be empty. Null on
	 *         error.
	 */
	@SuppressWarnings("unchecked")
	public Map<Pieces, String> login(String user, String password) throws MMLoginException {
		try {
			System.out.println("sending " + OpCode.LOGIN);
			conn.sendOpCode(OpCode.LOGIN);

			System.out.println("LOGIN: sending credentials");
			conn.out.writeUTF(user);
			conn.out.writeUTF(password);

			OpCode op = conn.readOpCode();
			System.out.println("LOGIN: response: " + op);
			if (op.equals(OpCode.SUCCESS)) {
				sessionKey = conn.in.readUTF();
				return null;
			} else if(op.equals(OpCode.RESTORE_GAME)) {
				sessionKey = conn.in.readUTF();
				Map<Pieces, String> save = (Map<Pieces, String>)conn.oin.readObject();
				return save;
			}
		} catch (Exception e) {
			System.err.println("login: " + e.getMessage());
		}
		throw new MMLoginException("error logging in");
	}

	/**
	 * Registers a player 
	 * @param p
	 * @return Boolean
	 */
	public boolean register(Player p) {
		try {
			System.out.println("sending " + OpCode.REGISTER);
			conn.sendOpCode(OpCode.REGISTER);
			conn.writeObject(p);
			conn.out.flush();

			OpCode op = conn.readOpCode();
			System.out.println("REGISTER response: " + op);
			return op.equals(OpCode.SUCCESS);
		} catch (IOException e) {
			System.err.println("register: " + e.getMessage());
		}
		return false;
	}
	
	/**
	 * Gets the stats of a user, specified by username
	 * @param username
	 * @return double
	 */
	public double getStats(String username) {
		try {
			System.out.println("sending " + OpCode.GET_STATS);
			conn.sendOpCode(OpCode.GET_STATS);

			conn.out.writeUTF(username);
			conn.out.writeUTF(sessionKey);

			OpCode op = conn.readOpCode();
			System.out.println("GET_STATS response: " + op);
			if (op.equals(OpCode.SUCCESS)) {
				return conn.in.readDouble();
			}

		} catch (IOException e) {
			System.err.println("getStats: " + e.getMessage());
		}
		return -1;
	}
	
	/**
	 * Used to fetch the leader board
	 * 
	 * @return Map of String user name to Double score
	 */
	@SuppressWarnings("unchecked")
	public Map<String, Double> getLeaderBoard() {
		try {
			System.out.println("sending " + OpCode.GET_LEADERBOARD);
			conn.sendOpCode(OpCode.GET_LEADERBOARD);
			OpCode op = conn.readOpCode();
			System.out.println("GET_LEADERBOARD response: " + op);
			if (op.equals(OpCode.SUCCESS)) {
				Map<String, Double> lb = (LinkedHashMap<String, Double>) conn
						.readObject();
				return lb;
			}
		} catch (IOException e) {
			System.err.println("getLeaderBoard: " + e.getMessage());
		} catch (ClassNotFoundException e) {
			System.err.println("getLeaderBoard: " + e.getMessage());
		}
		return null;
	}
	
	public class MMLoginException extends Exception {
		private static final long serialVersionUID = -2737846643495721024L;

		public MMLoginException(String message) {
			super(message);
		}
	}
}
